package view.editor;

import java.awt.Point;
import java.awt.geom.Rectangle2D;
import java.awt.print.PageFormat;
import java.awt.print.Paper;
import java.awt.print.PrinterJob;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Set;

import main.model.Attribute;
import main.model.Component;
import main.model.Constants;
import main.model.Entity;
import main.model.Hierarchy;
import main.model.Relation;

import com.mxgraph.model.mxCell;
import com.mxgraph.swing.mxGraphComponent;
import com.mxgraph.util.mxConstants;
import com.mxgraph.view.mxGraph;

import controller.editor.EditorController;

public class CanvasView extends mxGraph{

	private static CanvasView instance = null;
	private EditorController controller;

	private CanvasView(){
		super();
	}
	
	public static CanvasView getInstance() {
		if (instance == null){
			instance = new CanvasView();
		}
		return instance;
	}
	
	public void setController(EditorController controller){
		this.controller = controller;
	}
	
	public EditorController getController(){
		return this.controller;
	}
	
	@Override
	public boolean isCellMovable(Object cell) {
		mxCell v1 = (mxCell)cell;
		if (v1.isEdge()){
			return false;
		}
		return true;
	}
	
	@Override
	public boolean isCellResizable(Object cell) {
		return false;
	}
	
	public void repaintEntity(Entity entity, Point point){
		getModel().beginUpdate();
		Object parent = getDefaultParent();
		String color = null;
		if (!entity.isMasterEntity()){
			color = "fillColor=#f35959";
		}
		mxCell v1 = (mxCell)insertVertex(parent, null, entity.getName(), point.getX(), point.getY(), main.model.Constants.ENTITY_WIDTH,
				main.model.Constants.ENTITY_HEIGHT, color);

		Integer attCounter = 0;
		Iterator<Attribute> idIterator = entity.getIdAttributes().iterator();
		while (idIterator.hasNext()){
			Attribute att = idIterator.next();
			Point pointForComponent = this.controller.getPointForComponent(att);
			if (att.getAttributes() == null || att.getAttributes().isEmpty()){
				if (pointForComponent != null){
					repaintAttribute(v1, att, Constants.ATT_ID, attCounter, pointForComponent, null);
				} else {
					drawAttribute(v1, att, Constants.ATT_ID, attCounter, null);
				}
			} else {
				if (pointForComponent != null){
					repaintAttribute(v1, att, Constants.ATT_COMPLEX, attCounter, pointForComponent, Constants.ATT_ID);
				} else {
					drawAttribute(v1, att, Constants.ATT_COMPLEX, attCounter, Constants.ATT_ID);
				}
			}
			attCounter++;
		}

		Iterator<Attribute> attIterator = entity.getAttributes().iterator();
		while (attIterator.hasNext()){
			Attribute att = attIterator.next();
			Point pointForComponent = this.controller.getPointForComponent(att);
			if (att.getAttributes() == null || att.getAttributes().isEmpty()){
				if (pointForComponent != null){
					repaintAttribute(v1, att, Constants.ATT_COMMON, attCounter, pointForComponent, null);
				} else {
					drawAttribute(v1, att, Constants.ATT_COMMON, attCounter, null);
				}
			} else {
				if (pointForComponent != null){
					repaintAttribute(v1, att, Constants.ATT_COMPLEX, attCounter, pointForComponent, Constants.ATT_COMMON);
				} else {
					drawAttribute(v1, att, Constants.ATT_COMPLEX, attCounter, Constants.ATT_COMMON);
				}
			}
			attCounter++;
		}

		v1.setConnectable(Boolean.FALSE);
		this.controller.getCurrentLayout().bindComponentPoint(v1, entity);
		getModel().endUpdate();
	}

	private void repaintAttribute(mxCell component, Attribute att, Integer attType, Integer attCounter, Point point, Integer complexType){
		if (point == null){
			drawAttribute(component, att, attType, attCounter, complexType);
		} else {
			Object parent = getDefaultParent();
			mxCell v2;
			String attrName = att.getName();
			String style = "endArrow="+mxConstants.ARROW_OVAL + ";";
			if (!att.getCardinality().toString().equals("(1,1)")){
				attrName += att.getCardinality().toString();
			}
			if (att.getType().equals(Attribute.derivadoxcalc)){
				style += "strokeColor=#000000;";
			} else if (att.getType().equals(Attribute.derivadoxcopia)){
				style += "strokeColor=#FF0000;";
			}
			if (Constants.ATT_COMMON.equals(attType) || Constants.ATT_ID.equals(attType)){
				v2 = (mxCell)insertVertex(parent, null, attrName, point.getX(), point.getY(),
						1, 1, "shape=ellipse;perimeter=ellipsePerimeter");
				if (Constants.ATT_COMMON.equals(attType)){
					insertEdge(parent, null, "", component, v2, style + "endFill=0");
				} else {
					insertEdge(parent, null, "", component, v2, style + "endFill=1");
				}
			} else {
				v2 = (mxCell)insertVertex(parent, null, attrName, point.getX(), point.getY(),
						80, 40, "shape=ellipse;perimeter=ellipsePerimeter");
				if (Constants.ATT_ID.equals(complexType)){
					style += "endFill=1";
				} else {
					style += "endFill=0";
				}
				insertEdge(parent, null, "", component, v2, style);

				Integer innerCount = 0;
				Iterator<Attribute> idIterator = att.getIdAttributes().iterator();
				while (idIterator.hasNext()){
					Attribute innerAtt = idIterator.next();
					Point pointForComponent = this.controller.getPointForComponent(innerAtt);
					if (innerAtt.getAttributes() == null || innerAtt.getAttributes().isEmpty()){
						repaintAttribute(v2, innerAtt, Constants.ATT_ID, attCounter, pointForComponent, null);
					} else {
						repaintAttribute(v2, innerAtt, Constants.ATT_COMPLEX, attCounter, pointForComponent, Constants.ATT_ID);
					}
					innerCount++;
				}

				Iterator<Attribute> attIterator = att.getAttributes().iterator();
				while (attIterator.hasNext()){
					Attribute innerAtt = attIterator.next();
					Point pointForComponent = this.controller.getPointForComponent(innerAtt);
					if (innerAtt.getAttributes() == null || innerAtt.getAttributes().isEmpty()){
						repaintAttribute(v2, innerAtt, Constants.ATT_COMMON, attCounter, pointForComponent, null);
					} else {
						repaintAttribute(v2, innerAtt, Constants.ATT_COMPLEX, attCounter, pointForComponent, Constants.ATT_COMMON);
					}
					innerCount++;
				}
			}
			v2.setConnectable(Boolean.FALSE);
			this.controller.getCurrentLayout().bindComponentPoint(v2, att);
		}
	}
	
	public void repaintRelation(Relation rel, Point p) {
		getModel().beginUpdate();
		Object parent = getDefaultParent();
		String relStyle = "shape=rhombus;perimeter=rhombusPerimeter";
		if (rel.isComposition()){
			relStyle += ";fillColor=#f35959";
		}
		mxCell rhombus = (mxCell)insertVertex(parent, null, rel.getName(), p.getX(), p.getY(),
				Constants.RELATION_WIDTH, Constants.RELATION_HEIGHT, relStyle);
		Iterator<Entity> entIterator = rel.getEntities().iterator();
		while (entIterator.hasNext()){
			Entity entity = entIterator.next();
			String card = rel.getCardinalityForEntity(entity).toString();
			Point positionComponent = this.controller.getPointForComponent(entity);
			mxCell entityCell = this.controller.getCurrentLayout().getSelectedCell(positionComponent);
			String style = buildStyle(rhombus, entityCell);
			insertEdge(parent, null, card, rhombus, entityCell, style);
		}
		
		Integer attCounter = 0;
		Iterator<Attribute> idIterator = rel.getIdAttributes().iterator();
		while (idIterator.hasNext()){
			Attribute att = idIterator.next();
			Point pointForComponent = this.controller.getPointForComponent(att);
			if (att.getAttributes() == null || att.getAttributes().isEmpty()){
				repaintAttribute(rhombus, att, Constants.ATT_ID, attCounter, pointForComponent, null);
			} else {
				repaintAttribute(rhombus, att, Constants.ATT_COMPLEX, attCounter, pointForComponent, Constants.ATT_ID);
			}
			attCounter++;
		}

		Iterator<Attribute> attIterator = rel.getAttributes().iterator();
		while (attIterator.hasNext()){
			Attribute att = attIterator.next();
			Point pointForComponent = this.controller.getPointForComponent(att);
			if (att.getAttributes() == null || att.getAttributes().isEmpty()){
				repaintAttribute(rhombus, att, Constants.ATT_COMMON, attCounter, pointForComponent, null);
			} else {
				repaintAttribute(rhombus, att, Constants.ATT_COMPLEX, attCounter, pointForComponent, Constants.ATT_COMMON);
			}
			attCounter++;
		}
		
		rhombus.setConnectable(Boolean.FALSE);
		this.controller.getCurrentLayout().bindComponentPoint(rhombus, rel);
		getModel().endUpdate();
	}

	public void drawEntity(Entity entity, Point point){
		if (isEmptyPlace(point)){
			getModel().beginUpdate();
			Object parent = getDefaultParent();
			String color = null;
			if (!entity.isMasterEntity()){
				color = "fillColor=#f35959";
			}
			mxCell v1 = (mxCell)insertVertex(parent, null, entity.getName(), point.getX(), point.getY(), main.model.Constants.ENTITY_WIDTH,
					main.model.Constants.ENTITY_HEIGHT, color);
			Integer attCounter = 0;
			for (Attribute att : entity.getIdAttributes()) {
				if (att.getAttributes() == null || att.getAttributes().isEmpty()){
					drawAttribute(v1, att, Constants.ATT_ID, attCounter, null);
				} else {
					drawAttribute(v1, att, Constants.ATT_COMPLEX, attCounter, Constants.ATT_ID);
				}
				attCounter++;
			}
			
			for (Attribute att : entity.getAttributes()) {
				if (att.getAttributes() == null || att.getAttributes().isEmpty()){
					drawAttribute(v1, att, Constants.ATT_COMMON, attCounter, null);
				} else {
					drawAttribute(v1, att, Constants.ATT_COMPLEX, attCounter, Constants.ATT_COMMON);
				}
				attCounter++;
			}
			
			v1.setConnectable(Boolean.FALSE);
			getModel().endUpdate();
			this.controller.getCurrentLayout().bindComponentPoint(v1, entity);
		} else {
			System.out.println("not empty");
		}
	}


	public Boolean isValid(){
		
		Set<mxCell> keySet = this.controller.getCurrentLayout().getComponentInformation().keySet();
		List<Rectangle2D> list = new ArrayList<Rectangle2D>();
		for (mxCell mxCell : keySet) {
			Component component = this.controller.getCurrentLayout().getComponent(mxCell);
			if (component != null){
				if (component.isEntity() || component.isRelation() || component.isAttribute()){
					list.add(mxCell.getGeometry().getRectangle().getBounds2D());
				}
			}
		}
		Iterator<Rectangle2D> iterator = list.iterator();
		Boolean valid = Boolean.TRUE;
		while (iterator.hasNext() && valid){
			Rectangle2D rectangle = iterator.next();
			Iterator<Rectangle2D> iterator2 = list.iterator();
			while (iterator2.hasNext() && valid){
				Rectangle2D next = iterator2.next();
				if (!rectangle.equals(next) && rectangle.intersects(next)){
					valid = Boolean.FALSE;
				}
			}
		}
		return valid;
	}
	
	private boolean isEmptyPlace(Point point) {
		if (this.controller.getCurrentLayout().getSelectedCell(point) != null){
			return false;
		}
		return true;
	}
	
	public void setGraphComponent(mxGraphComponent graphComponent) {
		this.controller.getCurrentLayout().setGraphComponent(graphComponent);
	}

	public mxCell getSelectedCell(Point p){
		return this.controller.getCurrentLayout().getSelectedCell(p);
	}

	public void updateEntity(mxCell currentCell) {
		this.controller.getCurrentLayout().updateComponent(currentCell);
	}

	public Component getComponent(Point point) {
		return this.controller.getCurrentLayout().getComponent(point);
	}

	public void print(){
		PrinterJob pj = PrinterJob.getPrinterJob();
		if(pj.printDialog()){
			final mxGraphComponent graphComponent = new mxGraphComponent(this);
			PageFormat pf = graphComponent.getPageFormat();
			Paper paper = new Paper();
			double margin = 36;
			paper.setImageableArea(margin, margin, paper.getWidth() - margin * 2, paper.getHeight() - margin * 2);
			pf.setPaper(paper);
			pj.setPrintable(graphComponent, pf);          
			try          {            
				pj.print();
			}catch(Exception e){
				e.printStackTrace();
			}

		}
	}

	public void export(){
		//todo
	}

	public void drawRelation(Relation rel) {
		getModel().beginUpdate();
		Object parent = getDefaultParent();
		List<Entity> entitiesForRelation = rel.getEntities();
		Point p = getMiddlePointFromEntities(entitiesForRelation);
		String relStyle = "shape=rhombus;perimeter=rhombusPerimeter";
		if (rel.isComposition()){
			relStyle += ";fillColor=#f35959";
		}
		mxCell rhombus = (mxCell)insertVertex(parent, null, rel.getName(), p.getX() - (Constants.RELATION_WIDTH/2), p.getY() - (Constants.RELATION_HEIGHT/2),
				Constants.RELATION_WIDTH, Constants.RELATION_HEIGHT, relStyle);
		for (Entity entity : entitiesForRelation) {
			String card = rel.getCardinalityForEntity(entity).toString();
			Point positionComponent = this.controller.getPointForComponent(entity);
			mxCell entityCell = this.controller.getCurrentLayout().getSelectedCell(positionComponent);
			String style = buildStyle(rhombus, entityCell);
			insertEdge(parent, null, card, rhombus, entityCell, style);
		}
		
		Integer attCounter = 0;
		for (Attribute att : rel.getIdAttributes()) {
			if (att.getAttributes() == null || att.getAttributes().isEmpty()){
				drawAttribute(rhombus, att, Constants.ATT_ID, attCounter, null);
			} else {
				drawAttribute(rhombus, att, Constants.ATT_COMPLEX, attCounter, Constants.ATT_ID);
			}
			attCounter++;
		}
		
		for (Attribute att : rel.getAttributes()) {
			if (att.getAttributes() == null || att.getAttributes().isEmpty()){
				drawAttribute(rhombus, att, Constants.ATT_COMMON, attCounter, null);
			} else {
				drawAttribute(rhombus, att, Constants.ATT_COMPLEX, attCounter, Constants.ATT_COMMON);
			}
			attCounter++;
		}
		
		rhombus.setConnectable(Boolean.FALSE);
		this.controller.getCurrentLayout().bindComponentPoint(rhombus, rel);
		getModel().endUpdate();
	}

	private String buildStyle(mxCell rhombus, mxCell entityCell) {
		StringBuffer style = new StringBuffer();
		if (rhombus.getGeometry().getPoint().getX() < entityCell.getGeometry().getPoint().getX()){
			style.append("endArrow=none;edgeStyle=elbowEdgeStyle;elbow=horizontal;exitX=1;" +
			"exitY=0.5;exitPerimeter=1;entryX=0;entryY=0.5;entryPerimeter=0.5;");
		} else {
			style.append("endArrow=none;edgeStyle=elbowEdgeStyle;elbow=horizontal;exitX=0;" +
					"exitY=0.5;exitPerimeter=0;entryX=1;entryY=0.5;entryPerimeter=0.5;");
		}
		return style.toString();
	}

	private Point getMiddlePointFromEntities(List<Entity> entitiesForRelation) {
		Entity firstEntity = entitiesForRelation.get(0);
		Entity secondEntity = entitiesForRelation.get(1);
		Point firstPoint = new Point(controller.getPointForComponent(firstEntity));
		Point secondPoint = new Point(controller.getPointForComponent(secondEntity));
		mxCell firstCell = this.controller.getCurrentLayout().getSelectedCell(firstPoint);
		mxCell secondCell = this.controller.getCurrentLayout().getSelectedCell(secondPoint);
		Double x = getMiddlePoint(firstCell.getGeometry().getCenterX(), secondCell.getGeometry().getCenterX());
		Double y = getMiddlePoint(firstCell.getGeometry().getCenterY(), secondCell.getGeometry().getCenterY());
		return new Point(x.intValue(), y.intValue());
	} 

	private Double getMiddlePoint(Double one, Double two){
		return (one + two) / 2;
	}

	private void drawAttribute(mxCell component, Attribute att, Integer attType, Integer attCounter, Integer complexType){
		Object parent = getDefaultParent();
		Point point = component.getGeometry().getPoint();
		mxCell v2;
		String attrName = att.getName();
		String style = "endArrow="+mxConstants.ARROW_OVAL + ";";
		if (!att.getCardinality().toString().equals("(1,1)")){
			attrName += att.getCardinality().toString();
		}
		if (att.getType().equals(Attribute.derivadoxcalc)){
			style += "strokeColor=#000000;";
		} else if (att.getType().equals(Attribute.derivadoxcopia)){
			style += "strokeColor=#FF0000;";
		}
		if (Constants.ATT_COMMON.equals(attType) || Constants.ATT_ID.equals(attType)){
			v2 = (mxCell)insertVertex(parent, null, attrName, point.getX() + (80 - attCounter * 25), point.getY() - 40,
					1, 1, "shape=ellipse;perimeter=ellipsePerimeter");
			if (Constants.ATT_COMMON.equals(attType)){
				insertEdge(parent, null, "", component, v2, style + "endFill=0");
			} else {
				insertEdge(parent, null, "", component, v2, style +"endFill=1");
			}
		} else {
			v2 = (mxCell)insertVertex(parent, null, attrName, point.getX() + (80 - attCounter * 25), point.getY() - 40,
					80, 40, "shape=ellipse;perimeter=ellipsePerimeter");
			if (Constants.ATT_ID.equals(complexType)){
				style += "endFill=1";
			} else {
				style += "endFill=0";
			}
			insertEdge(parent, null, "", component, v2, style);
			Integer innerCount = 0;
			for (Attribute innerAtt : att.getIdAttributes()) {
				if (innerAtt.getAttributes() == null || innerAtt.getAttributes().isEmpty()){
					drawAttribute(v2, innerAtt, Constants.ATT_ID, innerCount, null);
				} else {
					drawAttribute(v2, innerAtt, Constants.ATT_COMPLEX, innerCount, Constants.ATT_ID);
				}
				innerCount++;
			}
			
			for (Attribute innerAtt : att.getAttributes()) {
				if (innerAtt.getAttributes() == null || innerAtt.getAttributes().isEmpty()){
					drawAttribute(v2, innerAtt, Constants.ATT_COMMON, innerCount, null);
				} else {
					drawAttribute(v2, innerAtt, Constants.ATT_COMPLEX, innerCount, Constants.ATT_COMMON);
				}
				innerCount++;
			}
		}
		v2.setConnectable(Boolean.FALSE);
		this.controller.getCurrentLayout().bindComponentPoint(v2, att);
		
	}

	public void drawHierarchy(Hierarchy h) {
		getModel().beginUpdate();
		Object parent = getDefaultParent();
		List<Entity> entitiesForRelation = h.getEntities();
		List<String> entidadesEspecializadas = new ArrayList<String>();
		for (Entity entity : entitiesForRelation) {
			entidadesEspecializadas.add(entity.getName());
		}
		
		String entidadGeneral = h.getParent().getName();
		Object entidadGeneralADibujar = null;
		Object[] childVertices = getChildVertices(parent);
		for (Object object : childVertices) {
			mxCell celda = (mxCell)object;
			String identificador = (String) celda.getValue();
			if (entidadGeneral.equals(identificador)){
				entidadGeneralADibujar = celda;
			}
		}
		
		for (Object object : childVertices) {
			mxCell celda = (mxCell) object;
			String identificador = (String) celda.getValue();
			
			if (entidadesEspecializadas.contains(identificador)){
				insertEdge(parent, null,h.getFormattedType(), celda, entidadGeneralADibujar,h.getName());
			}	
		}
		getModel().endUpdate();
	}
	
}